package org.jboss.edt;

import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.faces.context.FacesContext;

import org.ajax4jsf.model.DataVisitor;
import org.ajax4jsf.model.ExtendedDataModel;
import org.ajax4jsf.model.Range;
import org.ajax4jsf.model.SequenceRange;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.Create;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Scope;
import org.richfaces.component.Filterable;
import org.richfaces.component.Sortable2;
import org.richfaces.model.DataProvider;
import org.richfaces.model.ExtendedFilterField;
import org.richfaces.model.FilterField;
import org.richfaces.model.Modifiable;
import org.richfaces.model.SortField2;

@Name("PersonDataModel")
@Scope(ScopeType.SESSION)
public class PersonDataModel extends ExtendedDataModel implements Serializable,
		Modifiable {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	@In(value = "PersonDataProvider", create = true)
	private DataProvider<Person> dataProvider;

	private Object rowKey;
	private List<Object> wrappedKeys;
	private Map<Object, Person> wrappedData;
	private Integer rowCount;
	private Integer rowIndex;
	private SequenceRange memoRange;

	@Create
	public void init() {
		wrappedKeys = null;
		wrappedData = new HashMap<Object, Person>();
		rowCount = null;
		rowIndex = Integer.valueOf(-1);
		rowKey = null;

	}

	public Object getRowKey() {
		return rowKey;
	}

	public void setRowKey(Object key) {
		rowKey = key;
	}

	public void walk(FacesContext context, DataVisitor visitor, Range range,
			Object argument) throws IOException {
		int rowC = getRowCount();
		int firstRow = ((SequenceRange) range).getFirstRow();
		int numberOfRows = ((SequenceRange) range).getRows();
		if (numberOfRows <= 0) {
			numberOfRows = rowC;
		}

		if (wrappedKeys != null && memoRange != null
				&& memoRange.getFirstRow() == firstRow
				&& memoRange.getRows() == numberOfRows) {
			Object key;
			for (Iterator<Object> it = wrappedKeys.iterator(); it.hasNext(); visitor
					.process(context, key, argument)) {

				key = it.next();
				setRowKey(key);
			}

		} else {
			reset();
			wrappedKeys = new ArrayList<Object>();
			int endRow = firstRow + numberOfRows;
			if (endRow > rowC) {
				endRow = rowC;
			}

			Object key;
			for (Iterator<Person> it = loadData(firstRow, endRow).iterator(); it
					.hasNext(); visitor.process(context, key, argument)) {

				Person item = it.next();
				key = getKey(item);
				wrappedKeys.add(key);
				wrappedData.put(key, item);
			}

		}
		memoRange = (SequenceRange) range;
	}

	protected List<Person> loadData(int startRow, int endRow) {
		if (startRow < 0) {
			startRow = 0;
			throw new IllegalArgumentException((new StringBuilder())
					.append("Illegal start index value: ").append(startRow)
					.toString());
		}
		int rowCount = getRowCount();
		if (endRow > rowCount) {
			endRow = rowCount;
			throw new IllegalArgumentException((new StringBuilder())
					.append("Illegal end index value: ").append(endRow)
					.toString());
		}

		return dataProvider.getItemsByRange(startRow, endRow);
	}

	public int getRowCount() {
		if (rowCount == null) {
			rowCount = new Integer(dataProvider.getRowCount());
		} else {
			return rowCount.intValue();
		}

		return rowCount.intValue();
	}

	public Object getRowData() {
		if (rowKey == null) {
			return null;
		}

		return getObjectByKey(rowKey);
	}

	public Object getKey(Person o) {
		return dataProvider.getKey(o);
	}

	public Person getObjectByKey(Object key) {
		Person t = wrappedData.get(key);
		if (t == null) {
			t = dataProvider.getItemByKey(key);
			wrappedData.put(key, t);
		}
		return t;
	}

	public int getRowIndex() {
		return rowIndex.intValue();
	}

	public void setRowIndex(int rowIndex) {
		this.rowIndex = Integer.valueOf(rowIndex);
	}

	public Object getWrappedData() {
		throw new UnsupportedOperationException();
	}

	public void setWrappedData(Object data) {
		throw new UnsupportedOperationException();
	}

	public boolean isRowAvailable() {
		return getRowData() != null;
	}

	public void reset() {
		wrappedKeys = null;
		wrappedData.clear();
		rowCount = null;
		rowIndex = Integer.valueOf(-1);
		rowKey = null;
		memoRange = null;
	}

	public DataProvider<Person> getDataProvider() {
		return dataProvider;
	}

	public void setDataProvider(DataProvider<Person> dataProvider) {
		this.dataProvider = dataProvider;
	}

	public void modify(List<FilterField> filterFields,
			List<SortField2> sortFields) {
		List<SortField2> currentSortFields = null;
		List<FilterField> currentFilterFields = null;

		if (dataProvider instanceof Sortable2) {
			currentSortFields = ((Sortable2) dataProvider).getSortFields();
			((Sortable2) dataProvider).setSortFields(sortFields);
		}

		if (dataProvider instanceof Filterable) {
			currentFilterFields = ((Filterable) dataProvider).getFilterFields();
			((Filterable) dataProvider).setFilterFields(filterFields);
		}

		// Calculate whether filter fields or sort fields changed
		if (isEmptyFilterFields(filterFields)
				&& isEmptyFilterFields(currentFilterFields)
				&& isEmptySortFields(currentSortFields)
				&& isEmptySortFields(sortFields)) {
			// It was empty and is again empty -> don't reset
			return;
		}
		// Some filters are filled in, is there change?
		if (!isEmptyFilterFields(currentFilterFields)) {
			// It was filled
			currentFilterFields.removeAll(filterFields);
			if (!isEmptyFilterFields(currentFilterFields)) {
				// It is not empty, the filters were different
				reset();
				return;
			}
		} else if (!isEmptyFilterFields(filterFields)) {
			// It is filled now
			filterFields.removeAll(currentFilterFields);
			if (!isEmptyFilterFields(filterFields)) {
				// It is not empty, the filters were different
				reset();
				return;
			}
		}
		if (!isEmptySortFields(currentSortFields)) {
			// It was filled
			currentSortFields.removeAll(sortFields);
			if (!isEmptySortFields(currentSortFields)) {
				// It is not empty, the filters were different
				reset();
				return;
			}
		} else if (!isEmptySortFields(sortFields)) {
			// It is filled now
			sortFields.removeAll(currentSortFields);
			if (!isEmptySortFields(sortFields)) {
				// It is not empty, the filters were different
				reset();
				return;
			}
		}

	}

	/**
	 * Checks whether filter fields contain some elements
	 * 
	 * @param filterFields
	 * @return
	 */
	private boolean isEmptyFilterFields(List<FilterField> filterFields) {
		if (filterFields == null || filterFields.isEmpty()) {
			return true;
		}
		for (FilterField filterField : filterFields) {
			ExtendedFilterField eff = (ExtendedFilterField) filterField;
			if (eff.getFilterValue() != null) {
				return false;
			}
		}
		return true;
	}

	/**
	 * Checks whether sort fields contain some elements
	 * 
	 * @param sortFields
	 * @return
	 */
	private boolean isEmptySortFields(List<SortField2> sortFields) {
		if (sortFields == null || sortFields.isEmpty()) {
			return true;
		}
		return false;
	}
}